package com.niiwoo.civet.trade.service.local.loan.submit;

import com.alibaba.dubbo.common.utils.CollectionUtils;
import com.alibaba.fastjson.JSON;
import com.niiwoo.activity.stage.service.inviterBuddy.InviteBuddyActivityDubboService;
import com.niiwoo.activity.stage.service.prize.ActivityPrizeDubboService;
import com.niiwoo.asset.service.manager.R360LoanCommonDubboService;
import com.niiwoo.civet.account.dto.response.AccountBaseResponse;
import com.niiwoo.civet.account.enums.AccountActiveEnum;
import com.niiwoo.civet.account.enums.AccountRepaymentEnum;
import com.niiwoo.civet.account.service.AccountDubboService;
import com.niiwoo.civet.trade.dao.entity.*;
import com.niiwoo.civet.trade.dao.mapper.ProjectBorrowerPlanMapperExt;
import com.niiwoo.civet.trade.dao.mapper.ProjectBorrowerSummaryMapperExt;
import com.niiwoo.civet.trade.dao.mapper.ProjectMapperExt;
import com.niiwoo.civet.trade.dto.request.loan.*;
import com.niiwoo.civet.trade.dto.response.loan.LoanSubmitCommResDTO;
import com.niiwoo.civet.trade.enums.*;
import com.niiwoo.civet.trade.service.databackflow.TianChengDataBackFlowDubboService;
import com.niiwoo.civet.trade.service.local.common.ProjectCommonService;
import com.niiwoo.civet.trade.service.local.configure.TradeConfigLocalService;
import com.niiwoo.civet.trade.service.local.loan.LoanConfigService;
import com.niiwoo.civet.trade.service.local.loan.LoanQueryService;
import com.niiwoo.civet.trade.service.local.project.ProjectLocalService;
import com.niiwoo.civet.user.dto.UserPersonalDubboInfoDTO;
import com.niiwoo.civet.user.dto.UserProfessionDubboInfoDTO;
import com.niiwoo.civet.user.dto.response.*;
import com.niiwoo.civet.user.enums.UserForbidTypeEnum;
import com.niiwoo.civet.user.service.UserBasicInfoDubboService;
import com.niiwoo.civet.user.service.UserDataDubboService;
import com.niiwoo.civet.user.service.UserStatusDubboService;
import com.niiwoo.tripod.base.utils.DateUtils;
import com.niiwoo.tripod.base.utils.IdCardUtils;
import com.niiwoo.tripod.lanmao.enums.UserRoleEnum;
import com.niiwoo.tripod.provider.exception.BizException;
import lombok.extern.slf4j.Slf4j;
import org.apache.commons.lang3.time.FastDateFormat;
import org.joda.time.Days;
import org.joda.time.LocalDate;
import org.springframework.aop.framework.AopContext;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.data.redis.core.RedisTemplate;
import org.springframework.transaction.annotation.Transactional;
import org.springframework.util.StringUtils;

import java.math.BigDecimal;
import java.util.*;

/**
 * @Author: wuke
 * @Description:
 * @Date: Created in 16:57 2018/4/13
 */
@Slf4j
public abstract class AbstractLoanCheckService<I extends LoanSubmitReqDTO,O extends LoanSubmitCommResDTO> {

    protected UserDataDubboService userDataDubboService;
    protected ActivityPrizeDubboService activityPrizeDubboService;
    protected UserStatusDubboService userStatusDubboService;
    protected UserBasicInfoDubboService userBasicInfoDubboService;
    protected InviteBuddyActivityDubboService inviteBuddyActivityDubboService;
    protected TianChengDataBackFlowDubboService tianChengDataBackFlowDubboService;
    private R360LoanCommonDubboService r360LoanCommonDubboService;
    protected AccountDubboService accountDubboService;

    @Autowired
    protected LoanConfigService loanConfigService;
    @Autowired
    protected LoanQueryService loanQueryService;
    @Autowired
    protected ProjectCommonService projectCommonService;
    @Autowired
    protected ProjectLocalService projectLocalService;
    @Autowired
    protected TradeConfigLocalService tradeConfigLocalService;
    @Autowired
    protected ProjectMapperExt projectMapper;
    @Autowired
    protected ProjectBorrowerPlanMapperExt projectBorrowerPlanMapper;
    @Autowired
    protected ProjectBorrowerSummaryMapperExt projectBorrowerSummaryMapper;
    @Autowired
    protected RedisTemplate<String, String> redisTemplate;

    public static final String LOAN_QUANTITY_LIMIT_KEY = "loan_quantity_limit";

    //进入我的借款 检查
    public void intoCheck(I req, O res) {
        //检查当前是否存在退回标的
        isExistBackProjectCheck(req, res);

        commonCheck(req,res);
    }

    public void submitCheck(I req, O res) {
        //检查当前是否存在退回标的
        isExistBackProjectCheck(req, res);

        commonSubmitCheck(req,res);
    }

    //回退标的提交 检查
    public void backSubmitCheck(I req, O res) {
        //退回标的信息检查
        backProjectInfoCheck(req, res);

        commonSubmitCheck(req,res);
    }

    //回退标的提交 检查
    public void auditConfirmCheck(I req, O res) {
        //审核待确认标的信息检查
        auditConfirmProjectInfoCheck(req, res);

        //配置信息检查
        configCheck(req,res);

        //额度检查
        aviAmountCheck(req, res);

        //commonSubmitCheck(req,res);
    }

    //重新确认提交检查(提额认证后确认提交借款使用，用户修改了金额：原标的流标+新生成一个标的)
    public void reConfirmSubmitCheck(I req, O res) {
        //重新确认提交标的信息检查
        reConfirmProjectInfoCheck(req, res);

        //commonSubmitCheck(req,res);
    }

    //提交通用检查(新标的提交 + 回退标的提交)
    public void commonSubmitCheck(I req, O res){
        //账户信息检查
        accountInfoCheck(req, res);

        //借款每日总次数限制检查
        loanEverydayLoanLimitCheck(req, res);

        //用户信息检查
        userInfoCheck(req,res);

        //额度检查
        aviAmountCheck(req, res);

        //配置信息检查
        configCheck(req,res);

        //通用检查(进入我的借款 + 新标的提交 + 回退标的提交)
        commonCheck(req,res);
    }

    //通用检查(进入我的借款 + 新标的提交 + 回退标的提交)
    public void commonCheck(I req, O res) {
        //检查当前是否存在审核审核通过待确认标的
        isExistAuditConfirmCheck(req, res);

        //检查当前是否存在审核中标的
        isExistAuditingProjectCheck(req, res);

        //前一笔标的信息校验
        prevProjectInfoCheck(req, res);

        //年龄检查
        userAgeLimitCheck(req, res);

        //存在进件中融360标的检查
        isExsitRong360UserPushLoanDataCheck(req, res);

        //黑名单检查
        blackListCheck(req, res);

        //行为限制检查
        forbidCheck(req, res);

        //天秤借款限制检查
        libraLoanLimitCheck(req, res);

        //用户地址检查
        userCityCheck(req, res);

        //检查当前是否存在逾期未还标的
        isExistOverdueProjectCheck(req, res);

        //检查是否有历史记录中出现30天以上还款逾期标的
        isExistHisOverdue30DayProjetCheck(req, res);
    }

    private List<Integer> getDeadlineList(String loanDeadlines){
        if(!StringUtils.hasText(loanDeadlines)){
            return null;
        }
        String[] loanDeadlineArray = loanDeadlines.split(",");
        List<Integer> loanDeadlineList = new ArrayList<>();
        for(String s:loanDeadlineArray){
            Integer deadline = Integer.valueOf(s);
            loanDeadlineList.add(deadline);
        }
        return loanDeadlineList;
    }

    //配置信息检查
    private void configCheck(I req, O res){
        LoanBusinessInfo loanBusinessInfo = loanConfigService.selectBusinessInfo(getProjectType());
        log.info("commonSubmitCheck.loanBusinessInfo={}",JSON.toJSONString(loanBusinessInfo));
        if(loanBusinessInfo==null){
            excetipnResponseDeal(LoanLimitMessageEnum.LIMIT_10041, res);
        }

        if(Byte.valueOf("0").equals(loanBusinessInfo.getIsDisplay())){
            excetipnResponseDeal(LoanLimitMessageEnum.LIMIT_10051, res, loanBusinessInfo.getProductName());
        }

        //所选期限必须在后台配置范围之内
        List<Integer> deadlineList = getDeadlineList(loanBusinessInfo.getLoanDeadlines());
        if(CollectionUtils.isEmpty(deadlineList) || !deadlineList.contains(req.getDeadline())) {
            excetipnResponseDeal(LoanLimitMessageEnum.LIMIT_10042, res);
        }
    }

    private String getLoanQuantityLimitKeyPrefix(){
        return LOAN_QUANTITY_LIMIT_KEY+"_"+getProjectType().getValue()+"_";
    }

    private void userAgeLimitCheck(I req, O res) {
        String userId = req.getUserId();
        log.info("userAgeLimitCheck.userId={}",userId);
        //默认都为22-55
        int minAge = 22;
        int maxAge = 55;
        try {
            LoanBusinessConfig loanBusinessConfig = loanConfigService.selectBusinessConfig(getProjectType());

            log.info("userAgeLimitCheck.userId={},loanBusinessConfig={}",userId,JSON.toJSONString(loanBusinessConfig));

            if(loanBusinessConfig!=null){
                Short accessMinAge = loanBusinessConfig.getAccessMinAge();
                Short accessMaxAge = loanBusinessConfig.getAccessMaxAge();
                minAge = (accessMinAge==null?minAge:accessMinAge);
                maxAge = (accessMaxAge==null?maxAge:accessMaxAge);
            }
        } catch (Exception e) {
            log.error("userAgeLimitCheck异常，userId={}",userId,e);
        }

        String idCard =  loanQueryService.getUserIdCard(userId);

        log.info("userAgeLimitCheck.userId={},idCard={}",userId,idCard);

        if(!StringUtils.hasText(idCard)){
            return;
        }

        String birthDate = IdCardUtils.getBirthByIdCard(idCard);//获取生日

        log.info("userAgeLimitCheck.userId={},birthDate={}",userId,birthDate);

        if(!DateUtils.isValidAge(birthDate, minAge, maxAge)){
            excetipnResponseDeal(LoanLimitMessageEnum.LIMIT_10027, res, "借款", minAge, maxAge);
        }

        log.info("userAgeLimitCheck.userId={} end",userId);
    }

    private void userInfoCheck(I req, O res){
        //用户信息检查（基本信息、个人信息、职业信息、账户）
        UserInfoCheckResposeDTO userInfoCheckResposeDTO = userDataDubboService.userInfoCheck(req.getUserId());
        log.info("SpeedLoanDubboServiceImpl.userInfoCheck.userInfoCheckResposeDTO={}",JSON.toJSONString(userInfoCheckResposeDTO));
        int checkStatus = userInfoCheckResposeDTO.getCheckStatus();
        if ( LoanLimitMessageEnum.LIMIT_10000.getLimitCode()!=checkStatus) {
            res.setStatus(checkStatus);
            res.setAlertTitle(userInfoCheckResposeDTO.getAlertTitle());
            res.setAlertDesc(userInfoCheckResposeDTO.getAlertDesc());
            throw new BizException("TRD10006");
        }
    }

    //您在融360的借款申请正在处理中，了解详情请联系钱小二
    private void isExsitRong360UserPushLoanDataCheck(I req, O res) {
        String userId = req.getUserId();
        UserBasicInfoResponseDTO userBasicInfo = userBasicInfoDubboService.getUserBasicInfo(userId);
        boolean isExsitRong360UserPushLoanData = r360LoanCommonDubboService.isExsitRong360UserPushLoanData(userBasicInfo.getMobileNo());

        log.info("isExsitRong360UserPushLoanDataCheck.userId={},isExsitRong360UserPushLoanData={}",userId,isExsitRong360UserPushLoanData);

        if(isExsitRong360UserPushLoanData) {
            log.error("isExsitRong360UserPushLoanDataCheck.userId={},存在进件中融360标的",userId);
            excetipnResponseDeal(LoanLimitMessageEnum.LIMIT_20001, res);//存在进件中融360标的
        }
    }

    public void blackListCheck(I req, O res) {
        UserBlackListInfoDTO blackListInfoDTO = loanQueryService.getUserBlackListInfo(req.getUserId());

        log.info("blackListCheck.blackListInfoDTO={}",JSON.toJSONString(blackListInfoDTO));

        if (blackListInfoDTO == null) {
            return;
        }

        String improveScoreUrl = getTradeConfigValue(TradeConfigEnum.IMPROVE_SCORE_URL,"https://mp.weixin.qq.com/s/KB0tetWRfuYnr7m3SBM62Q");
        setExtendData(res,"extendUrl",improveScoreUrl);
        String args = getArgsForLIMIT_10031(blackListInfoDTO.getEndTime());
        excetipnResponseDeal(LoanLimitMessageEnum.LIMIT_10031, res, args);
    }

    protected String getTradeConfigValue(TradeConfigEnum configEnum,String defaultValue){
        String result = defaultValue;
        try {
            String value = tradeConfigLocalService.selectTradeConfigByEmun(configEnum);
            result = (StringUtils.hasText(value)?value:result);
        } catch (Exception e) {
            log.error("getTradeConfigValue异常，configEnum={}",JSON.toJSONString(configEnum),e);
        }
        return result;
    }

    private String getArgsForLIMIT_10031(Date time){
        String args = "";
        if (Days.daysBetween(new LocalDate(),new LocalDate(time)).getDays() < 60) {
            FastDateFormat fdf = FastDateFormat.getInstance("yyyy年MM月dd日");
            args = "\n请于" + fdf.format(time) + "后再来申请";
        }
        return args;
    }

    public void forbidCheck(I req, O res){
        ValidUserForbidResponseDTO validUserForbidResponseDTO = null;
        if (LoanSubmitTypeEnum.CREDIT_CARD_LOAN.equals(req.getLoanSubmitTypeEnum())) {
            validUserForbidResponseDTO = loanQueryService.queryValidUserForbidInfo(req.getUserId(), UserForbidTypeEnum.CREDITCARDLOAN);
        } else if (LoanSubmitTypeEnum.SOCIAL_SECURITY_LOAN.equals(req.getLoanSubmitTypeEnum())) {
            validUserForbidResponseDTO = loanQueryService.queryValidUserForbidInfo(req.getUserId(), UserForbidTypeEnum.SOCIALSECURITYLOAN);
        } else {
            validUserForbidResponseDTO = loanQueryService.queryValidUserForbidInfo(req.getUserId(), UserForbidTypeEnum.SPEEDBORROWED);
        }

        if (validUserForbidResponseDTO != null) {
            excetipnResponseDeal(LoanLimitMessageEnum.LIMIT_10003, res);//暂被限制申请借款！
        }
    }

    public void libraLoanLimitCheck(I req, O res){
        String userId = req.getUserId();
        UserLibraLoanLimitDTO userLibraLoanLimitDTO = loanQueryService.getUserLibraLoanLimit(userId, getProjectType());

        log.info("libraLoanLimitCheck.userLibraLoanLimitDTO={}",JSON.toJSONString(userLibraLoanLimitDTO));

        if (userLibraLoanLimitDTO == null) {
            return;
        }

        String improveScoreUrl = getTradeConfigValue(TradeConfigEnum.IMPROVE_SCORE_URL,"https://www.niiwoo.com/html5/app/explain/rejected/index.html");
        setExtendData(res,"extendUrl",improveScoreUrl);
        String args = getArgsForLIMIT_10031(userLibraLoanLimitDTO.getLimitedTime());
        excetipnResponseDeal(LoanLimitMessageEnum.LIMIT_10031, res, args);

        /*res.setStatus(LoanLimitMessageEnum.LIMIT_10031.getLimitCode());
        res.setAlertTitle(LoanLimitMessageEnum.LIMIT_10031.getLimitMessageTitle());
        if (Days.daysBetween(new LocalDate(userLibraLoanLimitDTO.getLimitedTime()), new LocalDate()).getDays() < 60) {
            res.setAlertDesc(createForbidTip(userLibraLoanLimitDTO.getLimitedTime()));
        } else {
            res.setAlertDesc("抱歉，您的综合信用评分不足，暂时无法借钱。\n\n如有疑问，请联系钱小二！");
        }
        throw new BizException("TRD10006");*/
    }

    public void userCityCheck(I req, O res){
        String userId = req.getUserId();

        LoanCityConfig loanCityConfig = loanConfigService.selectCityConfig(getProjectType());

        log.info("userCityCheck.loanCityConfig={}", JSON.toJSONString(loanCityConfig));

        if (loanCityConfig==null) {
            //this.excetipnResponseDeal(LoanLimitMessageEnum.LIMIT_10021, res);
            return;
        }

        String limitCityCode = loanCityConfig.getForbidCity();
        //未配置限制城市 则当全国开通
        if (StringUtils.isEmpty(limitCityCode)) {
            //this.excetipnResponseDeal(LoanLimitMessageEnum.LIMIT_10021, res);
            return;
        }

        try {
            //借款地检查
            loadAreaCheck(limitCityCode, req);
            //用户身份证所在地址检查
            idCardCheck(limitCityCode, userId);
            //居住地检查
            livingAreaCheck(limitCityCode, userId);
            //工作地地检查
            companyAreaCheck(limitCityCode, userId);
        } catch (BizException bizException) {
            excetipnResponseDeal(LoanLimitMessageEnum.LIMIT_10021, res);
        }
    }

    private void loadAreaCheck(String limitCityCode,I req){
        String userId = req.getUserId();
        log.info("loadAreaCheck start userId={}",userId);
        this.loanCityIsOpen(limitCityCode, req.getProvince(), req.getCity());
        log.info("loadAreaCheck end userId={}",userId);
    }

    //居住地检查
    private void livingAreaCheck(String limitCityCode, String userId) {
        log.info("livingAreaCheck.userId={},limitCityCode={} start...",userId,limitCityCode);
        //获取用户个人信息
        UserPersonalDubboInfoDTO personalInfo = loanQueryService.getUserPersonalInfo(userId);
        log.info("livingAreaCheck.userId={},limitCityCode={} personalInfo={}",userId,limitCityCode,JSON.toJSONString(personalInfo));
        if(personalInfo==null){
            log.info("用户未录入个人信息，不做校验");
            return;
        }
        this.loanCityIsOpen(limitCityCode, personalInfo.getLivingProvince(), personalInfo.getLivingCity());
        log.info("livingAreaCheck.userId={},limitCityCode={} end...",userId,limitCityCode);
    }

    //工作地地检查
    private void companyAreaCheck(String limitCityCode, String userId) {
        log.info("companyAreaCheck.userId={},limitCityCode={} start...",userId,limitCityCode);
        //获取职业信息
        UserProfessionDubboInfoDTO professionInfo = loanQueryService.getUserProfessionInfo(userId);
        log.info("companyAreaCheck.userId={},limitCityCode={},professionInfo={}",userId,limitCityCode,JSON.toJSONString(professionInfo));
        if(professionInfo==null){
            log.info("用户未录入职业信息，不做校验");
            return;
        }
        this.loanCityIsOpen(limitCityCode, professionInfo.getCompanyProvince(), professionInfo.getCompanyCity());
        log.info("companyAreaCheck.userId={},limitCityCode={} end...",userId,limitCityCode);
    }

    //用户身份证所在地址检查
    private void idCardCheck(String limitCityCode, String userId) {
        log.info("idCardCheck.userId={},limitCityCode={} start",userId,limitCityCode);
        String idCard = loanQueryService.getUserIdCard(userId);
        if(!StringUtils.hasText(idCard)){
            return;
        }

        String code = "";
        String provinceCode = idCard.substring(0, 2) + "0000";
        //'重庆市','天津市','北京市','上海市','香港特别行政区','澳门特别行政区'
        if(Arrays.asList("110000","120000","310000","500000","810000","820000")
                .contains(provinceCode)){
            code = provinceCode;
        }else{
            code = idCard.substring(0, 4) + "00";
        }

        if (limitCityCode.contains(code)) {
            log.error("idCardCheck error userId=" + userId + ",code=" + code);
            throw new BizException("TRD10006");// 所在地未开放借款服务！极速借
        }
    }

    //借款城市是否开放
    private void loanCityIsOpen(String limitCityCode, String province, String city) {
        log.info("loanCityIsOpen province=" + province + ",city=" + city);
        String areaCode = "";
        //直辖市用省份字段判断
        if (province.contains("北京") || province.contains("天津") || province.contains("上海") || province.contains("重庆") || province.contains("香港") || province.contains("澳门")) {
            areaCode = loanQueryService.selectAreaCodeByAreaName(province);
        } else if (city != null) {
            areaCode = loanQueryService.selectAreaCodeByAreaName(city);
        }
        log.info("loanCityIsOpen areaCode=" + areaCode);
        if (!StringUtils.isEmpty(areaCode) && limitCityCode.contains(areaCode)) {
            throw new BizException("TRD10006");// 所在地未开放借款服务
        }
    }

    //退回标的信息检查
    private void backProjectInfoCheck(I req, O res) {
        LoanBackSubmitReqDTO loanBackSubmitReqDTO = (LoanBackSubmitReqDTO)req;
        String userId = loanBackSubmitReqDTO.getUserId();
        Long projectId = loanBackSubmitReqDTO.getProjectId();

        Project project = projectMapper.selectProjectByUserIdProjectId(userId, projectId);

        log.info("backProjectInfoCheck.userId={},project={}",userId,JSON.toJSONString(project));

        if (project == null) {
            excetipnResponseDeal(LoanLimitMessageEnum.LIMIT_10045, res);
        }
        byte status = project.getStatus();
        Integer subStatus = project.getSubStatus();
        if (ProjectStatusEnum.CHECKING.getStatus().byteValue() != status ||
                (ProjectSubStatusEnum.CHECKING_BACK.getSubStatus() != subStatus
                        &&ProjectSubStatusEnum.CHECKING_BACK_PHONEOPERATOR_TIMEOUT.getSubStatus() != subStatus
                        &&ProjectSubStatusEnum.CHECKING_BACK_ADDRESSLIST_TIMEOUT.getSubStatus() != subStatus)) {
            excetipnResponseDeal(LoanLimitMessageEnum.LIMIT_10046, res);
        }
    }

    //审核待确认标的信息检查
    private void auditConfirmProjectInfoCheck(I req, O res) {
        LoanAuditConfirmDTO loanAuditConfirmDTO = (LoanAuditConfirmDTO)req;
        String userId = loanAuditConfirmDTO.getUserId();
        Long projectId = loanAuditConfirmDTO.getProjectId();

        Project project = projectMapper.selectProjectByUserIdProjectId(userId, projectId);

        log.info("auditConfirmProjectInfoCheck.userId={},project={}",userId,JSON.toJSONString(project));

        if (project == null) {
            excetipnResponseDeal(LoanLimitMessageEnum.LIMIT_10045, res);
        }
        byte status = project.getStatus();
        Integer subStatus = project.getSubStatus();
        if (!ProjectStatusEnum.CHECKING.getStatus().equals(status)
                || ProjectSubStatusEnum.CHECKING_FOR_CONFIRM.getSubStatus()!=subStatus) {
            excetipnResponseDeal(LoanLimitMessageEnum.LIMIT_10047, res);
        }
    }

    //重新确认提交标的信息检查(提额认证后确认提交借款使用，用户修改了金额：原标的流标+新生成一个标的))
    private void reConfirmProjectInfoCheck(I req, O res) {
        LoanReConfirmSubmitDTO loanReConfirmSubmitReqDTO = (LoanReConfirmSubmitDTO)req;
        String userId = loanReConfirmSubmitReqDTO.getUserId();
        Long projectId = loanReConfirmSubmitReqDTO.getProjectId();

        Project project = projectMapper.selectProjectByUserIdProjectId(userId, projectId);

        log.info("reConfirmProjectInfoCheck.userId={},project={}",userId,JSON.toJSONString(project));

        if (project == null) {
            excetipnResponseDeal(LoanLimitMessageEnum.LIMIT_10045, res);
        }
        byte status = project.getStatus();
        Integer subStatus = project.getSubStatus();
        if (ProjectStatusEnum.CHECKING.getStatus().byteValue() != status ||
                ProjectSubStatusEnum.CHECKING_FOR_CONFIRM.getSubStatus() != subStatus) {
            excetipnResponseDeal(LoanLimitMessageEnum.LIMIT_10047, res);
        }
    }

    /*private String createForbidTip(Date Time) {
        FastDateFormat fdf = FastDateFormat.getInstance("yyyy年MM月dd日");
        StringBuilder tipSb = new StringBuilder();
        tipSb.append("抱歉，您的综合信用评分不足，暂时无法借钱，请于");
        tipSb.append(fdf.format(Time));
        tipSb.append("后再来申请！\n\n如有疑问，请联系钱小二！");

        return tipSb.toString();
    }*/

    //获取抹零可用总额度
    protected BigDecimal getAviAmountRmZero(String userId){
        //获取剩余借款额度
        BigDecimal aviAmount = this.getAviAmount(userId);

        log.info("getAviAmountRmZero.userId={},aviAmount={}",userId,aviAmount);

        //提交时，判断本次借款金额是否大于用户的可借额度（50000减去在还本金=可借额度，可借额度精确到百位，采用抹零法），如果大于则提示用户：“对不起，您本次借款最多只能申请XXXX”
        //金额抹零
        BigDecimal aviAmountRmZero = aviAmount.divide(new BigDecimal("100")).setScale(0, BigDecimal.ROUND_DOWN).multiply(new BigDecimal("100"));

        log.info("getAviAmountRmZero.userId={},aviAmountRmZero={}",userId,aviAmountRmZero);

        return aviAmountRmZero;
    }

    //借款金额检查
    public void aviAmountCheck(I req, O res) {
        String userId = req.getUserId();

        LoanBusinessInfo loanBusinessInfo = loanConfigService.selectBusinessInfo(getProjectType());

        //获取抹零可用额度
        BigDecimal aviAmountRmZero = getAviAmountRmZero(userId);

        //获取最小借款额度
        BigDecimal minAmount = this.getMinAmount(loanBusinessInfo);

        //获取最大借款额度
        BigDecimal maxAmount = this.getMaxAmount(loanBusinessInfo);

        log.info("aviAmountCheck.userId={},minAmount={}",userId,minAmount);

        BigDecimal loanAmount = req.getLoanAmount(); //借款额度

		if (loanAmount.compareTo(minAmount) < 0) {
			log.info("aviAmountCheck.userId={},minAmount={},loanAmount={}", userId, minAmount, loanAmount);
			excetipnResponseDeal(LoanLimitMessageEnum.LIMIT_10058, res, aviAmountRmZero);
        }
		if (loanAmount.compareTo(aviAmountRmZero) > 0) {
			log.info("aviAmountCheck.userId={},aviAmount={},loanAmount={}", userId, aviAmountRmZero, loanAmount);
			excetipnResponseDeal(LoanLimitMessageEnum.LIMIT_10057, res, aviAmountRmZero);
        }
		if (loanAmount.compareTo(maxAmount) > 0) {
			log.info("aviAmountCheck.userId={},maxAmount={},loanAmount={}", userId, maxAmount, loanAmount);
			excetipnResponseDeal(LoanLimitMessageEnum.LIMIT_10057, res, maxAmount);
		}
		
        log.info("aviAmountCheck.userId={} end",userId);
    }

    private BigDecimal getAviAmount(String userId){
        String accumulativeTotalBowrrowLimitConfig = getTradeConfigValue(TradeConfigEnum.ACCUMULATIVETOTALBOWRROWLIMIT,"50000");
        BigDecimal accumulativeTotalBowrrowLimit = new BigDecimal(accumulativeTotalBowrrowLimitConfig);

        //已使用额度（所有产品）
        BigDecimal loanUsedAmout = projectCommonService.getLoanUsedAmout(userId);

        log.info("getAviAmount.userId={},loanUsedAmout={}",userId,loanUsedAmout);

        return accumulativeTotalBowrrowLimit.subtract(loanUsedAmout);
    }

    private BigDecimal getMinAmount(LoanBusinessInfo loanBusinessInfo){
        return loanBusinessInfo==null?BigDecimal.ZERO:loanBusinessInfo.getMinAmount();
    }

    private BigDecimal getMaxAmount(LoanBusinessInfo loanBusinessInfo){
        return loanBusinessInfo==null?BigDecimal.ZERO:loanBusinessInfo.getMaxAmount();
    }

    //检查当前是否存在逾期未还标的
    private void isExistOverdueProjectCheck(I req, O res) {
        String userId = req.getUserId();
        // 当前用户存在逾期未还的借款
        int overdueProjectCount = projectMapper.statisticsProjectCountByStatus(
                userId,
                Arrays.asList(ProjectStatusEnum.REPAYING.getStatus()),
                Arrays.asList(
                        ProjectSubStatusEnum.REPAYING_OVERDUE.getSubStatus(),
                        ProjectSubStatusEnum.REPAYING_BAD.getSubStatus())
        );

        log.info("isExistOverdueProjectCheck.userId={},overdueProjectCount={}",userId,overdueProjectCount);

        if (overdueProjectCount > 0) {
            String overdueConsequenceUrl = getTradeConfigValue(TradeConfigEnum.OVERDUE_CONSEQUENCE_URL,"https://bbs.niiwoo.com/thread-16426-1-1.html");
            setExtendData(res,"extendUrl",overdueConsequenceUrl);
            // 有逾期坏账借款标！（信用借或极速借）
            this.excetipnResponseDeal(LoanLimitMessageEnum.LIMIT_10008, res);
        }
    }

    //检查是否有历史记录中出现30天以上还款逾期标的
    private void isExistHisOverdue30DayProjetCheck(I req, O res) {
        String userId = req.getUserId();
        //int countOverdueDayOver30d = projectBorrowerPlanMapper.selectOverdueExceedConditionRepayment(userId,30);
        int countOverdueDayOver30d = projectMapper.statisticsUserOverdueExceedDay(userId, 30);

        log.info("isExistHisOverdue30DayProjetCheck.userId={},countOverdueDayOver30d={}",userId,countOverdueDayOver30d);

        if (countOverdueDayOver30d > 0) {
            //暂被限制申请借款
            String improveScoreUrl = getTradeConfigValue(TradeConfigEnum.IMPROVE_SCORE_URL,"https://www.niiwoo.com/html5/app/explain/rejected/index.html");
            setExtendData(res,"extendUrl",improveScoreUrl);
            excetipnResponseDeal(LoanLimitMessageEnum.LIMIT_10017, res);
        }
    }

    //检查当前是否存在退回标的
    private void isExistBackProjectCheck(I req, O res) {
        String userId = req.getUserId();
        //退回检查---必须在“审核中、待确认借款检查”之前，因为审核中状态包含退回状态

        Project backProject = projectMapper.speedLoanQueryForBack(userId);
        log.info("isExistBackProjectCheck.userId={},backProject={}",userId,JSON.toJSONString(backProject));

        if (backProject == null) {
            return;
        }

        Map extendData = new HashMap();
        extendData.put("projectId",backProject.getProjectId());
        extendData.put("projectTitle",backProject.getTitle());
        Date createTime = backProject.getCreateTime();
        String createTimeStr = createTime==null?null:(FastDateFormat.getInstance("yyyy-MM-dd").format(backProject.getCreateTime()));
        extendData.put("projectAddDate",createTimeStr);
        extendData.put("fromSource",backProject.getFromSource());
        res.setExtendData(extendData);
        //有借款标处于退回状态！
        this.excetipnResponseDeal(LoanLimitMessageEnum.LIMIT_10012, res);
    }

    //在途借款允许再次发起借款规则：前一笔借款期限≤3的，需至少还款一期才能再次发起借款；前一笔借款期限＞3的，需至少还款3期以上才能再次发起借款；（针对所有产品）
    public void prevProjectInfoCheck(I req, O res){
        String userId = req.getUserId();

        Project project = projectMapper.selectNewestProject(userId,
                Arrays.asList(ProjectStatusEnum.REPAYING, ProjectStatusEnum.FINISH));

        log.info("prevProjectInfoCheck.project={}",JSON.toJSONString(project));

        if(project==null){
            return;
        }
        //“已结清”则校验通过
        if(ProjectStatusEnum.FINISH.getStatus().equals(project.getStatus())){
            return;
        }
        ProjectBorrowerSummary projectBorrowerSummary = projectBorrowerSummaryMapper.selectByUserIdAndProjectId(userId,project.getProjectId());

        log.info("prevProjectInfoCheck.projectBorrowerSummary={}",JSON.toJSONString(projectBorrowerSummary));

        if(projectBorrowerSummary==null){
            return;
        }
        Byte repayStatus = projectBorrowerSummary.getRepayStatus();
        //如果“还款状态”为“已结清”，则说明此标的已全部结清，校验通过，无需在判断当前期数
        if(RepayStatusEnum.SETTLEMENT.getCode()==repayStatus.byteValue()){
            return;
        }

        Integer currentPeriod = projectBorrowerSummary.getCurrentPeriod();
        Integer totalPeriod = projectBorrowerSummary.getTotalPeriod();

        //尊敬的用户您好，请先还款3期以上才能再次发起借款申请。
        prevProjectDeadlineAboveCheck(currentPeriod, totalPeriod, res);

        //前一笔借款期限≤3的：尊敬的用户您好，请先还款1期以上才能再次发起借款申请。
        prevProjectDeadlineBelowCheck(currentPeriod, totalPeriod, res);
    }

    private void prevProjectDeadlineBelowCheck(Integer currentPeriod,Integer totalPeriod, O res){
        Map<String, String> configMap = tradeConfigLocalService.selectTradeConfigByEmuns(TradeConfigEnum.LASTBORROWDEADLINEBELOW, TradeConfigEnum.LASTBORROWDEADLINEBELOWLIMIT);
        if(configMap==null){
            return;
        }
        String lastBorrowDeadlineBelowConfig = configMap.get(TradeConfigEnum.LASTBORROWDEADLINEBELOW.getConfigKey());
        String lastBorrowDeadlineBelowLimitConfig = configMap.get(TradeConfigEnum.LASTBORROWDEADLINEBELOWLIMIT.getConfigKey());
        if(!StringUtils.hasText(lastBorrowDeadlineBelowConfig) || !StringUtils.hasText(lastBorrowDeadlineBelowLimitConfig)){
            return;
        }

        Integer lastBorrowDeadlineBelow = Integer.valueOf(lastBorrowDeadlineBelowConfig);
        Integer lastBorrowDeadlineBelowLimit = Integer.valueOf(lastBorrowDeadlineBelowLimitConfig);

        //前一笔借款期限≤3的：尊敬的用户您好，请先还款1期以上才能再次发起借款申请。
        if(totalPeriod<=lastBorrowDeadlineBelow && currentPeriod<(lastBorrowDeadlineBelowLimit+1)){
            this.excetipnResponseDeal(LoanLimitMessageEnum.LIMIT_10049, res, lastBorrowDeadlineBelowLimit);
        }
    }

    private void prevProjectDeadlineAboveCheck(Integer currentPeriod,Integer totalPeriod, O res){
        Map<String, String> configMap = tradeConfigLocalService.selectTradeConfigByEmuns(TradeConfigEnum.LASTBORROWDEADLINEABOVE, TradeConfigEnum.LASTBORROWDEADLINEABOVELIMIT);
        if(configMap==null){
            return;
        }
        String lastBorrowDeadlineAboveConfig = configMap.get(TradeConfigEnum.LASTBORROWDEADLINEABOVE.getConfigKey());
        String lastBorrowDeadlineAboveLimitConfig = configMap.get(TradeConfigEnum.LASTBORROWDEADLINEABOVELIMIT.getConfigKey());
        if(!StringUtils.hasText(lastBorrowDeadlineAboveConfig) || !StringUtils.hasText(lastBorrowDeadlineAboveLimitConfig)){
            return;
        }

        Integer lastBorrowDeadlineAbove = Integer.valueOf(lastBorrowDeadlineAboveConfig);
        Integer lastBorrowDeadlineAboveLimit = Integer.valueOf(lastBorrowDeadlineAboveLimitConfig);

        //尊敬的用户您好，请先还款3期以上才能再次发起借款申请。
        if(totalPeriod>lastBorrowDeadlineAbove && currentPeriod<(lastBorrowDeadlineAboveLimit+1)){
            this.excetipnResponseDeal(LoanLimitMessageEnum.LIMIT_10049, res, lastBorrowDeadlineAboveLimit);
        }
    }

    private void setExtendData(O res,String extendKey,Object extendValue){
        Map<String, Object> extendData = res.getExtendData();
        extendData = extendData==null?new HashMap<>():extendData;
        extendData.put(extendKey,extendValue);
        res.setExtendData(extendData);
    }

    //检查当前是否存在审核审核通过待确认标的
    public void isExistAuditConfirmCheck(I req, O res){
        getSelfProxy().isExistAuditConfirmProject(req,res);
    }

    //检查当前是否存在审核审核通过待确认标的
    @Transactional
    public void isExistAuditConfirmProject(I req, O res) {
        String userId = req.getUserId();
        int count = projectMapper.statisticsProjectCountByStatus(
                userId, Arrays.asList(ProjectStatusEnum.CHECKING.getStatus(),ProjectStatusEnum.COLLECTING.getStatus()),
                Arrays.asList(ProjectSubStatusEnum.CHECKING_FOR_CONFIRM.getSubStatus())
        );
        log.info("isExistAuditConfirmProject.userId={},count={}",userId,count);
        if (count > 0) {
            // 有借款标处于待确认状态
            this.excetipnResponseDeal(LoanLimitMessageEnum.LIMIT_10055, res);
        }
    }

    //检查当前是否存在审核中标的(不包括退回状态、审核通过待确认状态)
    public void isExistAuditingProjectCheck(I req, O res){
        getSelfProxy().isExistAuditingProject(req,res);
    }

    //检查当前是否存在审核中标的(不包括退回状态、审核通过待确认状态)
    @Transactional
    public void isExistAuditingProject(I req, O res) {
        String userId = req.getUserId();
        //审核中、待确认借款检查
        int auditingProjectCount = projectMapper.statisticsProjectCountByStatus(
                userId, Arrays.asList(ProjectStatusEnum.CHECKING.getStatus(),ProjectStatusEnum.COLLECTING.getStatus()),
                Arrays.asList(ProjectSubStatusEnum.CHECKING_INIT.getSubStatus(),
                        ProjectSubStatusEnum.CHECKING_LOAN_FAKE_PASS.getSubStatus(),
                        ProjectSubStatusEnum.CHECKING_AUTO_SUCCESS.getSubStatus(),
                        ProjectSubStatusEnum.CHECKING_AUTO_MANUAL.getSubStatus(),
                        ProjectSubStatusEnum.CHECKING_MANUAL_SUCCESS.getSubStatus(),
                        ProjectSubStatusEnum.CHECKING_MANUAL_FIRST.getSubStatus(),
                        ProjectSubStatusEnum.CHECKING_MANUAL_LAST.getSubStatus(),
                        ProjectSubStatusEnum.CHECKING_MANUAL_BACK_FIRST.getSubStatus(),
                        ProjectSubStatusEnum.CHECKING_CREDIT_AMOUNT.getSubStatus(),
                        ProjectSubStatusEnum.COLLECTING_INIT.getSubStatus(),
                        ProjectSubStatusEnum.COLLECTING_LENDING.getSubStatus(),
                        ProjectSubStatusEnum.CHECKING_FOR_CONFIRMED.getSubStatus())
        );
        log.info("isExistAuditingProject.userId={},auditingProjectCount={}",userId,auditingProjectCount);
        if (auditingProjectCount > 0) {
            // 有借款标在审核中！
            this.excetipnResponseDeal(LoanLimitMessageEnum.LIMIT_10006, res);
        }
        /*Project record = new Project();
        record.setProjectId(2222222222l);
        record.setStatus(ProjectStatusEnum.CHECKING.getStatus());
        projectMapper.updateByPrimaryKeySelective(record);
        int i = 1/0;*/
    }

    public void accountInfoCheck(I req, O res){
        String userId = req.getUserId();
        AccountBaseResponse accountBase = loanQueryService.getAccountBase(userId, UserRoleEnum.BORROWERS);
        log.info("accountInfoCheck.userId={},accountBase={}",userId, JSON.toJSONString(accountBase));
        if(accountBase==null){
            excetipnResponseDeal(LoanLimitMessageEnum.LIMIT_10052, res);
        }
        if(!AccountActiveEnum.ACTIVATED.getValue().equals(accountBase.getImportUserActiva())){
            excetipnResponseDeal(LoanLimitMessageEnum.LIMIT_10053, res);
        }
        if(!AccountRepaymentEnum.OPEN.getValue().equals(accountBase.getRepayment())){
            excetipnResponseDeal(LoanLimitMessageEnum.LIMIT_10054, res);
        }
    }

    /**
     * 借款每日总次数限制检查
     * @param req
     * @param res
     */
    public void loanEverydayLoanLimitCheck(I req, O res) {
        Integer deadline = req.getDeadline();
        LoanQuantityConfig loanQuantityConfig = loanConfigService.selectLoanQuantityConfig(getProjectType().getValue(),deadline);
        if(!this.isNeedCheckForLoanEverydayLoanLimit(deadline,loanQuantityConfig)){
            return;
        }
        int loanQuantityLimit = 0;
        for(int i =loanQuantityConfig.getMinDeadline(); i<=loanQuantityConfig.getMaxDeadline(); i++){
            String loanQuantityLimitKey = getLoanQuantityLimitKeyPrefix() + String.valueOf(i) + FastDateFormat.getInstance("yyyyMMdd").format(new Date());
            String tempLimitCount = redisTemplate.opsForValue().get(loanQuantityLimitKey);
            if(StringUtils.hasText(tempLimitCount)){
                loanQuantityLimit = loanQuantityLimit + Integer.valueOf(tempLimitCount);
            }
        }
        if(loanQuantityLimit == 0){
            return;
        }

        Integer singleDayLoans = loanQuantityConfig.getSingleDayLoans();
        if(loanQuantityLimit>=singleDayLoans){
            excetipnResponseDeal(LoanLimitMessageEnum.LIMIT_10044, res, loanQuantityConfig.getNoticeContent());
        }
    }

    private boolean isNeedCheckForLoanEverydayLoanLimit(int deadline,LoanQuantityConfig loanQuantityConfig){
        if(loanQuantityConfig==null){
            return false;
        }
        Integer minDeadline = loanQuantityConfig.getMinDeadline();
        Integer maxDeadline = loanQuantityConfig.getMaxDeadline();
        Integer singleDayLoans = loanQuantityConfig.getSingleDayLoans();

        if(minDeadline==null || maxDeadline==null || singleDayLoans==null){
            return false;
        }
        if(minDeadline>deadline || maxDeadline<deadline){
            return false; //借款期限不在限制期限范围，则不做处理
        }
        return true;
    }

    //异常结果处理
    protected void excetipnResponseDeal(LoanLimitMessageEnum loanLimitMessageEnum, O res, Object...args) {
        String alertTitle=String.format(loanLimitMessageEnum.getLimitMessageTitle(), args);
        String alertDesc=String.format(loanLimitMessageEnum.getLimitMessageDesc(), args);
        res.setStatus(loanLimitMessageEnum.getLimitCode());
        res.setAlertTitle(alertTitle);
        res.setAlertDesc(alertDesc);
        throw new BizException("TRD10006");
    }

    protected ProjectTypeEnum getProjectType(){
        return getLoanSubmitMapEnum().getProjectTypeEnum();
    }

    protected abstract LoanSubmitTypeEnum getLoanSubmitMapEnum();

    protected AbstractLoanCheckService getSelfProxy(){
        return (AbstractLoanCheckService)AopContext.currentProxy();
    }

    public void setUserDataDubboService(UserDataDubboService userDataDubboService) {
        this.userDataDubboService = userDataDubboService;
    }

    public void setActivityPrizeDubboService(ActivityPrizeDubboService activityPrizeDubboService) {
        this.activityPrizeDubboService = activityPrizeDubboService;
    }

    public void setUserStatusDubboService(UserStatusDubboService userStatusDubboService) {
        this.userStatusDubboService = userStatusDubboService;
    }

    public void setUserBasicInfoDubboService(UserBasicInfoDubboService userBasicInfoDubboService) {
        this.userBasicInfoDubboService = userBasicInfoDubboService;
    }

    public void setInviteBuddyActivityDubboService(InviteBuddyActivityDubboService inviteBuddyActivityDubboService) {
        this.inviteBuddyActivityDubboService = inviteBuddyActivityDubboService;
    }

    public void setTianChengDataBackFlowDubboService(TianChengDataBackFlowDubboService tianChengDataBackFlowDubboService) {
        this.tianChengDataBackFlowDubboService = tianChengDataBackFlowDubboService;
    }

    public void setR360LoanCommonDubboService(R360LoanCommonDubboService r360LoanCommonDubboService) {
        this.r360LoanCommonDubboService = r360LoanCommonDubboService;
    }

    public void setAccountDubboService(AccountDubboService accountDubboService) {
        this.accountDubboService = accountDubboService;
    }
}
